<?php
// app/Http/Middleware/RequestResponseLogger.php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
use Symfony\Component\HttpFoundation\Response;

class RequestResponseLogger
{
    // 排除日志记录的路径
    protected $except = [
        '/health',
        '/favicon.ico',
        '/status'
    ];

    // 需要脱敏的敏感字段
    protected $sensitiveFields = [
//        'password', 'password_confirmation', 'credit_card',
//        'cvv', 'token', 'api_key', 'secret', 'authorization'
    ];

    // 最大日志体长度 (字节)
    protected $maxLogSize = 2048 * 5;

    /**
     * 处理传入请求
     */
    public function handle(Request $request, Closure $next): mixed
    {
        // 检查是否在排除路径中
        if ($this->shouldSkipLogging($request)) {
            return $next($request);
        }
        // 生成唯一请求ID (用于关联请求和响应)
        $requestId = (string)Str::uuid();
        // 将请求ID附加到请求对象，供响应阶段使用
        $request->attributes->set('request_id', $requestId);
        // 记录请求信息到临时存储
        $this->storeRequestData($request, $requestId);
        // 继续请求处理管道
        return $next($request);
    }

    /**
     * 响应发送后终止处理
     */
    public function terminate(Request $request, Response $response): void
    {
        // 检查是否在排除路径中
        if ($this->shouldSkipLogging($request)) {
            return;
        }
        // 获取请求ID
        $requestId = $request->attributes->get('request_id');
        // 仅在FPM环境下使用异步写入 (不影响CLI/其他环境)
        if (function_exists('fastcgi_finish_request')) {
            fastcgi_finish_request(); // 释放请求到客户端
        }
        // 记录组合日志 (请求+响应)
        $this->logCombinedEntry($request, $response, $requestId);
    }

    /**
     * 判断是否跳过日志记录
     */
    protected function shouldSkipLogging(Request $request): bool
    {
        // 跳过静态文件、健康检查等
        if (in_array($request->path(), $this->except, true)) {
            return true;
        }
        // 跳过OPTIONS方法 (预检请求)
        return $request->isMethod('OPTIONS');
    }

    /**
     * 存储请求数据到临时属性
     */
    protected function storeRequestData(Request $request, string $requestId): void
    {
        $request->attributes->set('log_data', [
            'id' => $requestId,
            'time' => microtime(true),
            'ip' => $request->ip(),
            'method' => $request->method(),
            'url' => $request->fullUrl(),
            'headers' => $this->filterHeaders($request->headers->all()),
            'input' => $this->filterSensitiveData($request->except($this->sensitiveFields)),
            'content' => $this->truncateContent($request->getContent())
        ]);
    }

    /**
     * 记录组合日志条目
     */
    protected function logCombinedEntry(Request $request, Response $response, string $requestId): void
    {
        // 获取请求数据
        $requestData = $request->attributes->get('log_data');
        // 计算请求处理时间 (毫秒)
        $duration = round((microtime(true) - $requestData['time']) * 1000, 2);
        // 准备响应数据
        $responseData = [
            'status' => $response->getStatusCode(),
            'headers' => $this->filterHeaders($response->headers->all()),
            'size' => $this->getResponseSize($response),
            'content' => $this->truncateContent($response->getContent()),
            'duration_ms' => $duration
        ];
        // 构建结构化日志条目
        $logEntry = [
            'request_id' => $requestId,
            'client_ip' => $requestData['ip'],
            'method' => $requestData['method'],
            'url' => $requestData['url'],
            'status' => $responseData['status'],
            'duration' => $duration,
            'request' => [
                'headers' => $requestData['headers'],
                'input' => $requestData['input'],
                'body' => (json_decode($requestData['content'], true) != null) ? json_decode($requestData['content'], true) : $requestData['content'],
            ],
            'response' => [
                'headers' => $responseData['headers'],
                'size_bytes' => $responseData['size'],
                'body' => (json_decode($responseData['content'], true) != null) ? json_decode($responseData['content'], true) : $responseData['content'],
            ]
        ];
        // 使用专用日志通道写入
        if ($request->is("api_admin/*")) {
            Log::channel('api_admin')->info('API Request-Response', $logEntry);
        } elseif ($request->is("api_client/*")) {
            Log::channel('api_client')->info('API Request-Response', $logEntry);
        }else{
            Log::channel('daily')->info('API Request-Response', $logEntry);
        }
    }

    /**
     * 过滤HTTP头信息 (移除敏感信息)
     */
    protected function filterHeaders(array $headers): array
    {
        return collect($headers)
            ->mapWithKeys(function ($values, $name) {
                // 简化头值为单个字符串
                $value = is_array($values) ? implode(', ', $values) : $values;
                // 过滤授权头
                if (strtolower($name) === 'authorization') {
                    $value = '******';
                }
                return [strtolower($name) => $value];
            })
            ->only(['host', 'user-agent', 'content-type', 'accept', 'referer']) // 仅保留关键头
            ->toArray();
    }

    /**
     * 过滤敏感数据
     */
    protected function filterSensitiveData(array $data): array
    {
        // 递归过滤敏感字段
        array_walk_recursive($data, function (&$value, $key) {
            if (in_array(strtolower($key), $this->sensitiveFields, true)) {
                $value = '******';
            }
        });
        return $data;
    }

    /**
     * 截断过长的内容
     */
    protected function truncateContent(?string $content): string
    {
        if (empty($content)) {
            return '';
        }
        // 检测二进制内容
        if (!preg_match('//u', $content)) {
            return '[BINARY DATA]';
        }
        // 截断过长的文本
        return mb_substr($content, 0, $this->maxLogSize, 'UTF-8');
    }

    /**
     * 获取响应大小
     */
    protected function getResponseSize(Response $response): int
    {
        // 对于文件响应直接获取大小
        if ($response instanceof \Illuminate\Http\BinaryFileResponse) {
            return filesize($response->getFile()->getPathname());
        }
        // 对于其他响应计算内容长度
        return strlen($response->getContent());
    }
}
